Skip to content

인풋 공톰 컴포넌트 구현#13

Closed
jungwon123 wants to merge 4 commits into
mainfrom
feat/input
Closed

인풋 공톰 컴포넌트 구현#13
jungwon123 wants to merge 4 commits into
mainfrom
feat/input

Conversation

@jungwon123

@jungwon123 jungwon123 commented Jan 31, 2026

Copy link
Copy Markdown
Contributor

Summary

atoms 와 molecules로 작업을 하다가 통일성이 안맞아서 처음 pull 해온 컴포넌트 폴더 형식에 맞추어 작업했습니다.
제가 정의했던 인풋 atom은 아래 이미지 2개이고 나머지는 아이콘, 버튼 조합의 molecule로 생각했습니다.
image
image

구현된 컴포넌트

  • 이메일 인풋
  • 비밀번호 인풋
  • 텍스트 박스 인풋
  • 댓글 인풋
  • 읽기 전용 인풋

Issue

#4

@jungwon123 jungwon123 added this to the 컴포넌트 milestone Jan 31, 2026
@jungwon123 jungwon123 added the 기능추가 기능개발 및 추가할 때 사용해주세요. label Jan 31, 2026
@github-project-automation github-project-automation Bot moved this to 개발 대기중 in workers Jan 31, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @jungwon123, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 애플리케이션 전반에 걸쳐 사용될 공통 인풋 컴포넌트 세트를 도입하여 사용자 인터페이스의 일관성을 강화하고 개발 효율성을 높이는 것을 목표로 합니다. 다양한 입력 필드에 대한 표준화된 솔루션을 제공함으로써, 향후 개발에서 재사용성을 증대시키고 디자인 시스템의 통일성을 유지할 수 있습니다.

Highlights

  • 새로운 인풋 컴포넌트 구현: 이메일 인풋, 비밀번호 인풋, 텍스트 박스 인풋, 댓글 인풋, 읽기 전용 인풋 등 다양한 유형의 공통 인풋 컴포넌트가 추가되었습니다.
  • 컴포넌트 구조 통일성 확보: 기존의 atoms 및 molecules 방식에서 벗어나, 초기 컴포넌트 폴더 형식에 맞춰 작업하여 컴포넌트 구조의 통일성을 높였습니다.
  • 비밀번호 가시성 토글 기능: PasswordInput 컴포넌트에 비밀번호를 보이거나 숨길 수 있는 토글 버튼 기능이 추가되었습니다.
  • 자동 크기 조절 및 전송 버튼이 있는 텍스트 영역: ActionTextArea 컴포넌트는 입력 내용에 따라 자동으로 높이가 조절되며, 입력값이 있을 때 활성화되는 전송 버튼을 포함합니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

전반적으로 공통 인풋 컴포넌트들을 체계적으로 잘 구현해주셨습니다. 몇 가지 중요한 수정사항과 개선점을 제안드립니다. 특히, ref를 컴포넌트에 전달하기 위한 React.forwardRef의 사용법을 확인해주시면 좋겠습니다. 이는 react-hook-form과 같은 라이브러리와의 통합에 필수적입니다. 또한, 웹 접근성 향상을 위해 <label><input>을 연결하는 부분과 사용자 경험을 개선할 수 있는 몇 가지 사항에 대한 피드백을 포함했습니다.

Comment on lines +1 to +13
import clsx from 'clsx';
import { InputProps } from './types/types';
import styles from './styles/Input.module.css';

/**
* 공통 Input 컴포넌트.
* @param className 추가 CSS 클래스
* @param ref 외부에서 전달하는 ref (react-hook-form 등)
* @param props 네이티브 input의 모든 속성(placeholder, type, onChange 등)
*/
export default function Input({ className, ref, ...props }: InputProps) {
return <input ref={ref} className={clsx(styles.input, className)} {...props} />;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

함수 컴포넌트에서 ref를 props로 직접 받아 전달할 수 없습니다. ref를 올바르게 전달하기 위해서는 React.forwardRef를 사용해야 합니다. react-hook-form과 같은 라이브러리에서 ref를 사용하려면 이 수정이 필수적입니다.

Suggested change
import clsx from 'clsx';
import { InputProps } from './types/types';
import styles from './styles/Input.module.css';
/**
* 공통 Input 컴포넌트.
* @param className 추가 CSS 클래스
* @param ref 외부에서 전달하는 ref (react-hook-form )
* @param props 네이티브 input의 모든 속성(placeholder, type, onChange )
*/
export default function Input({ className, ref, ...props }: InputProps) {
return <input ref={ref} className={clsx(styles.input, className)} {...props} />;
}
import { forwardRef } from 'react';
import clsx from 'clsx';
import { InputProps } from './types/types';
import styles from './styles/Input.module.css';
/**
* 공통 Input 컴포넌트.
* @param className 추가 CSS 클래스
* @param props 네이티브 input의 모든 속성(placeholder, type, onChange )
*/
const Input = forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {
return <input ref={ref} className={clsx(styles.input, className)} {...props} />;
});
Input.displayName = 'Input';
export default Input;

Comment on lines +1 to +44
'use client';

import { useState } from 'react';
import Image from 'next/image';
import clsx from 'clsx';
import Input from './Input';
import { PasswordInputProps } from './types/types';
import visibilityTrue from '@/assets/icons/visibility/visibillityTrue.svg';
import visibilityFalse from '@/assets/icons/visibility/visibillityFalse.svg';
import styles from './styles/PasswordInput.module.css';

/**
* 비밀번호 Input 컴포넌트.
* @param className 추가 CSS 클래스
* @param ref 외부에서 전달하는 ref (react-hook-form 등)
* @param props 네이티브 input의 모든 속성(type 제외)
*/
export default function PasswordInput({ className, ref, ...props }: PasswordInputProps) {
const [showPassword, setShowPassword] = useState(false);

return (
<div className={styles.wrapper}>
<Input
ref={ref}
type={showPassword ? 'text' : 'password'}
className={clsx(styles.input, className)}
{...props}
/>
<button
type="button"
className={styles.toggleButton}
onClick={() => setShowPassword((prev) => !prev)}
aria-label={showPassword ? '비밀번호 숨기기' : '비밀번호 보기'}
>
<Image
src={showPassword ? visibilityTrue : visibilityFalse}
alt=""
width={24}
height={24}
/>
</button>
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

부모 컴포넌트로부터 ref를 받아 내부 Input 컴포넌트로 전달하려면 PasswordInput 컴포넌트도 React.forwardRef로 감싸야 합니다. 이는 react-hook-form과 같은 라이브러리와의 연동을 위해 중요합니다.

'use client';

import { forwardRef, useState } from 'react';
import Image from 'next/image';
import clsx from 'clsx';
import Input from './Input';
import { PasswordInputProps } from './types/types';
import visibilityTrue from '@/assets/icons/visibility/visibillityTrue.svg';
import visibilityFalse from '@/assets/icons/visibility/visibillityFalse.svg';
import styles from './styles/PasswordInput.module.css';

/**
 * 비밀번호 Input 컴포넌트.
 * @param className 추가 CSS 클래스
 * @param props 네이티브 input의 모든 속성(type 제외)
 */
const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
  ({ className, ...props }, ref) => {
    const [showPassword, setShowPassword] = useState(false);

    return (
      <div className={styles.wrapper}>
        <Input
          ref={ref}
          type={showPassword ? 'text' : 'password'}
          className={clsx(styles.input, className)}
          {...props}
        />
        <button
          type="button"
          className={styles.toggleButton}
          onClick={() => setShowPassword((prev) => !prev)}
          aria-label={showPassword ? '비밀번호 숨기기' : '비밀번호 보기'}
        >
          <Image
            src={showPassword ? visibilityTrue : visibilityFalse}
            alt=""
            width={24}
            height={24}
          />
        </button>
      </div>
    );
  },
);

PasswordInput.displayName = 'PasswordInput';

export default PasswordInput;

Comment on lines +1 to +13
import clsx from 'clsx';
import { TextAreaProps } from './types/types';
import styles from './styles/TextArea.module.css';

/**
* 멀티라인 입력 컴포넌트.
* @param className 추가 CSS 클래스
* @param ref 외부에서 전달하는 ref (react-hook-form 등)
* @param props 네이티브 textarea의 모든 속성(placeholder, rows, onChange 등)
*/
export default function TextArea({ className, ref, ...props }: TextAreaProps) {
return <textarea ref={ref} className={clsx(styles.textarea, className)} {...props} />;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

함수 컴포넌트에서 ref를 props로 직접 받아 전달할 수 없습니다. ref를 올바르게 전달하기 위해서는 React.forwardRef를 사용해야 합니다. react-hook-form과 같은 라이브러리에서 ref를 사용하려면 이 수정이 필수적입니다.

Suggested change
import clsx from 'clsx';
import { TextAreaProps } from './types/types';
import styles from './styles/TextArea.module.css';
/**
* 멀티라인 입력 컴포넌트.
* @param className 추가 CSS 클래스
* @param ref 외부에서 전달하는 ref (react-hook-form )
* @param props 네이티브 textarea의 모든 속성(placeholder, rows, onChange )
*/
export default function TextArea({ className, ref, ...props }: TextAreaProps) {
return <textarea ref={ref} className={clsx(styles.textarea, className)} {...props} />;
}
import { forwardRef } from 'react';
import clsx from 'clsx';
import { TextAreaProps } from './types/types';
import styles from './styles/TextArea.module.css';
/**
* 멀티라인 입력 컴포넌트.
* @param className 추가 CSS 클래스
* @param props 네이티브 textarea의 모든 속성(placeholder, rows, onChange )
*/
const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(({ className, ...props }, ref) => {
return <textarea ref={ref} className={clsx(styles.textarea, className)} {...props} />;
});
TextArea.displayName = 'TextArea';
export default TextArea;

Comment thread src/components/input/types/types.ts Outdated
Comment on lines +1 to +28
import { ComponentPropsWithRef, ReactNode } from 'react';

export type InputProps = ComponentPropsWithRef<'input'>;

export type PasswordInputProps = Omit<ComponentPropsWithRef<'input'>, 'type'>;

export type TextAreaProps = ComponentPropsWithRef<'textarea'>;

export type PasswordFieldProps = Omit<ComponentPropsWithRef<'input'>, 'type'>;

export type ActionTextAreaProps = TextAreaProps & {
onSubmit?: () => void;
wrapperClassName?: string;
};

export type CommentInputProps = Omit<ActionTextAreaProps, 'wrapperClassName'>;

export type AccountInputProps = {
email?: string;
children?: ReactNode;
};

export type ChangePasswordProps = {
isEditing?: boolean;
newPasswordProps?: PasswordFieldProps;
confirmPasswordProps?: PasswordFieldProps;
children?: ReactNode;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

여러 타입을 정리하고 ref 전달 문제를 해결하기 위해 다음과 같이 수정을 제안합니다:

  1. ComponentPropsWithRef 대신 ComponentProps를 사용하여 forwardRef와 함께 사용하기 좋게 만듭니다. refforwardRef가 별도로 처리해줍니다.
  2. 중복되는 PasswordFieldProps 타입을 제거하고 PasswordInputProps로 통일합니다.
import { ComponentProps, ReactNode } from 'react';

export type InputProps = ComponentProps<'input'>;

export type PasswordInputProps = Omit<ComponentProps<'input'>, 'type'>;

export type TextAreaProps = ComponentProps<'textarea'>;

export type ActionTextAreaProps = TextAreaProps & {
  onSubmit?: () => void;
  wrapperClassName?: string;
};

export type CommentInputProps = Omit<ActionTextAreaProps, 'wrapperClassName'>;

export type AccountInputProps = {
  email?: string;
  children?: ReactNode;
};

export type ChangePasswordProps = {
  isEditing?: boolean;
  newPasswordProps?: PasswordInputProps;
  confirmPasswordProps?: PasswordInputProps;
  children?: ReactNode;
};

Comment on lines +1 to +41
import Input from './Input';
import { ChangePasswordProps } from './types/types';
import styles from './styles/ChangePassword.module.css';

/**
* 비밀번호 변경 컴포넌트.
* @param isEditing 편집 모드 여부 (false면 인풋 비활성화, 기본값 false)
* @param newPasswordProps 새 비밀번호 인풋에 전달할 props
* @param confirmPasswordProps 새 비밀번호 확인 인풋에 전달할 props
* @param children 버튼 등 하단 영역에 렌더링할 요소
*/
export default function ChangePassword({
isEditing = false,
newPasswordProps,
confirmPasswordProps,
children,
}: ChangePasswordProps) {
return (
<div className={styles.container}>
<div className={styles.field}>
<label className={styles.label}>새 비밀번호</label>
<Input
type="password"
placeholder="새 비밀번호를 입력해 주세요."
disabled={!isEditing}
{...newPasswordProps}
/>
</div>
<div className={styles.field}>
<label className={styles.label}>새 비밀번호 확인</label>
<Input
type="password"
placeholder="새 비밀번호를 다시 한 번 입력해 주세요."
disabled={!isEditing}
{...confirmPasswordProps}
/>
</div>
<div className={styles.buttonArea}>{children}</div>
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

ChangePassword 컴포넌트를 개선할 몇 가지 제안사항이 있습니다:

  1. PasswordInput 사용: 현재 Input 컴포넌트를 직접 사용하고 있는데, 비밀번호 가시성 토글 기능이 있는 PasswordInput 컴포넌트를 사용하면 사용자 경험이 향상됩니다.
  2. 웹 접근성: <label><input>htmlForid 속성으로 연결하여 스크린 리더 사용자들이 더 쉽게 양식을 이용할 수 있도록 해야 합니다.
import PasswordInput from './PasswordInput';
import { ChangePasswordProps } from './types/types';
import styles from './styles/ChangePassword.module.css';

/**
 * 비밀번호 변경 컴포넌트.
 * @param isEditing 편집 모드 여부 (false면 인풋 비활성화, 기본값 false)
 * @param newPasswordProps 새 비밀번호 인풋에 전달할 props
 * @param confirmPasswordProps 새 비밀번호 확인 인풋에 전달할 props
 * @param children 버튼 등 하단 영역에 렌더링할 요소
 */
export default function ChangePassword({
  isEditing = false,
  newPasswordProps,
  confirmPasswordProps,
  children,
}: ChangePasswordProps) {
  return (
    <div className={styles.container}>
      <div className={styles.field}>
        <label className={styles.label} htmlFor="new-password">새 비밀번호</label>
        <PasswordInput
          id="new-password"
          placeholder="새 비밀번호를 입력해 주세요."
          disabled={!isEditing}
          {...newPasswordProps}
        />
      </div>
      <div className={styles.field}>
        <label className={styles.label} htmlFor="confirm-password">새 비밀번호 확인</label>
        <PasswordInput
          id="confirm-password"
          placeholder="새 비밀번호를 다시 한 번 입력해 주세요."
          disabled={!isEditing}
          {...confirmPasswordProps}
        />
      </div>
      <div className={styles.buttonArea}>{children}</div>
    </div>
  );
}

Comment on lines +14 to +21
<div className={styles.field}>
<label className={styles.label}>이메일</label>
<Input type="email" value={email} disabled className={styles.readOnly} />
</div>
<div className={styles.field}>
<label className={styles.label}>비밀번호</label>
<Input type="password" value="••••••••" disabled className={styles.readOnly} />
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

두 가지 개선점을 제안합니다:

  1. 웹 접근성: 스크린 리더 사용자의 편의를 위해 <label>htmlFor 속성과 <input>id 속성을 연결하여 접근성을 향상시켜야 합니다.
  2. 제어되지 않는 컴포넌트 오류 방지: email prop이 undefined일 경우, React에서 제어되지 않는(uncontrolled) input이 제어되는(controlled) input으로 변경된다는 경고가 발생할 수 있습니다. value={email ?? ''}와 같이 기본값을 제공하여 이를 방지할 수 있습니다.
Suggested change
<div className={styles.field}>
<label className={styles.label}>이메일</label>
<Input type="email" value={email} disabled className={styles.readOnly} />
</div>
<div className={styles.field}>
<label className={styles.label}>비밀번호</label>
<Input type="password" value="••••••••" disabled className={styles.readOnly} />
</div>
<div className={styles.field}>
<label htmlFor="account-email" className={styles.label}>이메일</label>
<Input id="account-email" type="email" value={email ?? ''} disabled className={styles.readOnly} />
</div>
<div className={styles.field}>
<label htmlFor="account-password" className={styles.label}>비밀번호</label>
<Input id="account-password" type="password" value="••••••••" disabled className={styles.readOnly} />
</div>

rows={1}
className={clsx(styles.textarea, className)}
onChange={(e) => {
setHasValue(e.target.value.length > 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

현재는 공백만 입력해도 전송 버튼이 활성화됩니다. trim()을 사용하여 문자열의 양 끝 공백을 제거한 후 길이를 확인하면, 의미있는 입력이 있을 때만 버튼이 활성화되도록 개선할 수 있습니다.

Suggested change
setHasValue(e.target.value.length > 0);
setHasValue(e.target.value.trim().length > 0);

@jungwon123 jungwon123 moved this from 개발 대기중 to 개발 진행중 in workers Jan 31, 2026
@jungwon123 jungwon123 moved this from 개발 진행중 to PR 리뷰중 in workers Jan 31, 2026
@jungwon123 jungwon123 closed this Jan 31, 2026
@github-project-automation github-project-automation Bot moved this from PR 리뷰중 to 개발 완료 in workers Jan 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

기능추가 기능개발 및 추가할 때 사용해주세요.

Projects

Status: 개발 완료

Development

Successfully merging this pull request may close these issues.

4 participants